﻿/*
Copyright (c) 2010 The Developer Journey Company

Permission is hereby granted, free of charge, to any person obtaining a copy 
of this software and associated documentation files (the "Software"), to deal 
in the Software without restriction, including without limitation the rights 
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
copies of the Software, and to permit persons to whom the Software is 
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all 
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
THE SOFTWARE. 
*/

using System;
using System.ComponentModel;
using CommunityMegaphone.Services;
using System.Linq;
using System.Xml.Linq;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.IO.IsolatedStorage;
using System.Text;

namespace CommunityMegaphone.ViewModels
{
    public class ReferenceViewModel : NotifyPropertyChangedBase
    {
        private EventManagerSoapClient _eventManagerClient;

        public ReferenceViewModel()
        {
            _eventManagerClient = new EventManagerSoapClient("EventManagerSoap");
            _eventManagerClient.GetStatesCompleted += OnGetStatesCompleted;
            _eventManagerClient.GetEventTypesCompleted += OnGetEventTypesCompleted;

            _eventTypesMap = new Dictionary<string, EventTypeViewModel>();
            _statesMap = new Dictionary<string, StateViewModel>();

            LoadReferenceData();
        }

        public void LoadReferenceData()
        {
            LoadStates();
            LoadEventTypes();
        }

        private string CacheFileName(string baseName, double maxCacheDays)
        {
            try
            {
                var stg = IsolatedStorageFile.GetUserStoreForApplication();
                foreach (var fileName in stg.GetFileNames("*.xml"))
                {
                    if (!fileName.StartsWith(baseName))
                        continue;
                    // there is a cached file - has it expired?
                    string[] parts = fileName.Split('_', '.');
                    DateTime savedAt = DateTime.FromFileTimeUtc(Int64.Parse(parts[1]));
                    double span = (DateTime.UtcNow - savedAt).TotalDays;
                    if (span < maxCacheDays)
                        return fileName;
                }
            }
            catch { }
            return null;
        }

        private IEnumerable<XElement> CacheFileXml(string fileName, string nodeName)
        {
            var stg = IsolatedStorageFile.GetUserStoreForApplication();
            using (var stream = stg.OpenFile(fileName, FileMode.Open,
                FileAccess.Read, FileShare.None))
            {
                if (stream.Length <= Int32.MaxValue)
                {
                    int length = (int)stream.Length;
                    byte[] data = new byte[length];
                    int read = stream.Read(data, 0, length);
                    if (read == length)
                    {
                        // remove the XML declaration
                        var xml = Encoding.UTF8.GetString(data, 0, length)
                            .Substring(41);
                        var elem = XElement.Parse(xml);
                        return elem.Descendants(nodeName);
                    }
                }
            }
            return null;
        }

        #region State Members
        private bool _statesLoaded = false;

        public ObservableCollection<StateViewModel> States
        {
            get
            {
                return _statesMap.Select(kvp => kvp.Value).ToObservableCollection();
            }

            set
            {
                if ((object)value == null)
                    return;
                foreach (var svm in value)
                {
                    if (_eventTypesMap.ContainsKey(svm.Code))
                        _eventTypesMap.Remove(svm.Code);
                    _statesMap.Add(svm.Code, svm);
                }
                NotifyPropertyChanged("States");
                NotifyPropertyChanged("StatesMap");
            }
        }

        private Dictionary<string, StateViewModel> _statesMap;
        public Dictionary<string, StateViewModel> StatesMap
        {
            get
            {
                return _statesMap;
            }
        }

        private const double CACHEDAYS_STATES = 7.0d;
        private void LoadStates()
        {
            // don't load the objects more than once per lifetime
            if (_statesLoaded)
                return;

            try
            {
                var fileName = CacheFileName("states_", CACHEDAYS_STATES);
                if (fileName == null)
                {
                    // no usable cache file exists
                    // call the web service
                    var client = new EventManagerSoapClient("EventManagerSoap");
                    try { _eventManagerClient.GetStatesAsync(); }
                    catch { }
                }
                else
                {
                    // get the cache file contents as a sequence of XElements
                    var seq = CacheFileXml(fileName, "state");

                    // simulate the data from the SOAP service
                    var states = from state in seq
                                 let code = state.Attribute("code").Value
                                 let name = state.Attribute("name").Value
                                 select new StateItem()
                                 {
                                     Code = code,
                                     Name = name
                                 };

                    // sending "this" as the sender will be the signal
                    // that the cache does not need to be updated
                    OnGetStatesCompleted(this,
                        new GetStatesCompletedEventArgs(
                            new object[] { states.ToArray() },
                            null, false, null));
                }
            }
            catch { }
        }

        private void OnGetStatesCompleted(object sender, GetStatesCompletedEventArgs e)
        {
            if (e.Error != null || e.Cancelled || e.Result == null || e.Result.Length == 0)
                return;

            // only cache when the sender is not "this"
            if (sender != this)
                CacheStates(e.Result);

            var states = from item in e.Result
                         let code = (item.Code == null) ? null : item.Code.Trim()
                         let name = (item.Name == null) ? null : item.Name.Trim()
                         where code != null && name != null
                         select new StateViewModel
                         {
                             Code = code,
                             Name = name
                         };

            foreach (var svm in states)
            {
                if (_statesMap.ContainsKey(svm.Code))
                    _statesMap.Remove(svm.Code);
                _statesMap.Add(svm.Code, svm);
            }

            NotifyPropertyChanged("StatesMap");
            _statesLoaded = true;
        }

        private void CacheStates(StateItem[] states)
        {
            var stg = IsolatedStorageFile.GetUserStoreForApplication();

            // delete any existing cache file(s)
            foreach (var fileName in stg.GetFileNames("*.xml")
                .Where(n => n.StartsWith("states_")))
            {
                stg.DeleteFile(fileName);
            }

            // are there any to cache?
            if (states == null || states.Length == 0)
                return;

            var newFileName = "states_" + DateTime.UtcNow.ToFileTimeUtc() + ".xml";
            using (var stream = stg.CreateFile(newFileName))
            {
                var doc = XDocument.Parse("<states></states>");
                foreach (var elem in states
                    .Where(si => si.Code != null && si.Name != null)
                    .Select(si => new XElement("state",
                                      new XAttribute("code", si.Code),
                                      new XAttribute("name", si.Name))))
                {
                    doc.Root.Add(elem);
                }
                doc.Save(stream);
            }
        }
        #endregion State Members

        #region EventType Members
        private bool _eventTypesLoaded = false;

        public ObservableCollection<EventTypeViewModel> EventTypes
        {
            get
            {
                return _eventTypesMap.Select(kvp => kvp.Value).ToObservableCollection();
            }

            set
            {
                if ((object)value == null) 
                    return;
                foreach (var etvm in value)
                {
                    if (_eventTypesMap.ContainsKey(etvm.ID))
                        _eventTypesMap.Remove(etvm.ID);
                    _eventTypesMap.Add(etvm.ID, etvm);
                }
                NotifyPropertyChanged("EventTypes");
                NotifyPropertyChanged("EventTypesMap");
            }
        }

        private Dictionary<string, EventTypeViewModel> _eventTypesMap;
        public Dictionary<string, EventTypeViewModel> EventTypesMap
        {
            get
            {
                return _eventTypesMap;
            }
        }

        private const double CACHEDAYS_EVENTTYPES = 7.0d;
        private void LoadEventTypes()
        {
            // don't load the objects more than once per lifetime
            if (_eventTypesLoaded)
                return;

            try
            {
                var fileName = CacheFileName("eventtypes_", CACHEDAYS_STATES);
                if (fileName == null)
                {
                    // no usable cache file exists
                    // call the web service
                    var client = new EventManagerSoapClient("EventManagerSoap");
                    try { _eventManagerClient.GetEventTypesAsync(); }
                    catch { }
                }
                else
                {
                    // get the cache file contents as a sequence of XElements
                    var seq = CacheFileXml(fileName, "eventtype");

                    // simulate the data from the SOAP service
                    var eventTypes = from eventType in seq
                                     let id = eventType.Attribute("id").Value
                                     let inPerson = Boolean.Parse(eventType.Attribute("inperson").Value)
                                     let name = eventType.Attribute("name").Value
                                     select new EventTypeItem()
                                     {
                                         ID = id,
                                         InPerson = inPerson,
                                         Name = name
                                     };

                    // sending "this" as the sender will be the signal
                    // that the cache does not need to be updated
                    OnGetEventTypesCompleted(this,
                        new GetEventTypesCompletedEventArgs(
                            new object[] { eventTypes.ToArray() },
                            null, false, null));
                }
            }
            catch { }
        }

        private void OnGetEventTypesCompleted(object sender, GetEventTypesCompletedEventArgs e)
        {
            if (e.Error != null || e.Cancelled || e.Result == null || e.Result.Length == 0)
                return;

            // only cache when the sender is not "this"
            if (sender != this)
                CacheEventTypes(e.Result);

            var eventTypes = from item in e.Result
                             let id = (item.ID == null) ? null : item.ID.Trim()
                             let inPerson = item.InPerson
                             let name = (item.Name == null) ? null : item.Name.Trim()
                             where id != null && name != null
                             select new EventTypeViewModel
                             {
                                 ID = id,
                                 InPerson = inPerson,
                                 Name = name
                             };

            foreach (var etvm in eventTypes)
            {
                if (_eventTypesMap.ContainsKey(etvm.ID))
                    _eventTypesMap.Remove(etvm.ID);
                _eventTypesMap.Add(etvm.ID, etvm);
            }

            NotifyPropertyChanged("EventTypesMap");
            _eventTypesLoaded = true;
        }

        private void CacheEventTypes(EventTypeItem[] eventTypes)
        {
            var stg = IsolatedStorageFile.GetUserStoreForApplication();

            // delete any existing cache file(s)
            foreach (var fileName in stg.GetFileNames("*.xml")
                .Where(n => n.StartsWith("eventtypes_")))
            {
                stg.DeleteFile(fileName);
            }

            // are there any to cache?
            if (eventTypes == null || eventTypes.Length == 0)
                return;

            var newFileName = "eventtypes_" + DateTime.UtcNow.ToFileTimeUtc() + ".xml";
            using (var stream = stg.CreateFile(newFileName))
            {
                var doc = XDocument.Parse("<eventtypes></eventtypes>");
                foreach (var elem in eventTypes
                    .Where(eti => eti.ID != null && eti.Name != null)
                    .Select(eti => new XElement("eventtype",
                                      new XAttribute("id", eti.ID),
                                      new XAttribute("inperson", eti.InPerson),
                                      new XAttribute("name", eti.Name))))
                {
                    doc.Root.Add(elem);
                }
                doc.Save(stream);
            }
        }
        #endregion EventType Members
    }
}
